<?php
// TODO: enable support for default actions of controllers, i.e. if one says: URL/controller/  - that should
//       be parsable and default some kind of action should be invoked.    

class ParamsParser implements IteratorAggregate  {
  private  $cmdLine;
  private  $runQueue;
  private  $currToken;
  private  $c; // counter
  private  $numTokens; // number of commands
  private static $instance;
  private $outOfBoundsState = false;
  private static $debug = false;
  
  private function __construct($pParam = NULL) {
    if (!is_null($pParam)) $this->setCmdLine($pParam);
  }
  
  public function setCmdLine( $pCmdLine) {
    $this->cmdLine =  $pCmdLine;
    if (strlen($this->cmdLine)>0 && $this->cmdLine[strlen($this->cmdLine)-1] == "/") $this->cmdLine = substr($this->cmdLine, 0 , -1);
    $this->runQueue = explode("/", $this->cmdLine);
    $this->numTokens = count($this->runQueue);
    //self::next();
    $this->currToken = $this->runQueue[0];
  }
  
  public function getCmdLine() {
    return $this->cmdLine;
  }
  
  
  public  function setRunQueueFromArray(Array $pTokens) {
    $this->runQueue = $pTokens;
  }
  
  public  function setDoneQueue(Array $pTokens) {
    $this->doneQueue = $pTokens;
  }
  
  public  function currToken() {
    if ($this->outOfBoundsState) throw new OutOfBoundsException("Last next() call went out of bounds, currToken is undefined"); 
    return $this->currToken;
  }
  
  public  function next() {
    if ( $this->c == $this->numTokens - 1 ) {
      $this->outOfBoundsState = true;
      throw new OutOfBoundsException("Can't fetch next token"); 
    }
    $this->currToken = $this->runQueue[++$this->c];
  }
  
  public function hasNext() {
    if ($this->c < $this->numTokens - 1) return true;
    return false;
  }
  
  public function nextHasNext() {
    if ($this->hasNext()) $this->next();
    return $this->hasNext();
  }
  
  public function reset() {
    $this->c = 0;
    $this->currToken = $this->runQueue[0];
    $this->outOfBoundsState = false;
  }
  
  public function getIterator() {
    return new ArrayIterator($this->runQueue);
  }

  public static function getInstance($pParam = NULL) {
    if ( (self::$instance instanceof ParamsParser) == false ) {
      if (is_null($pParam)) throw new ParamsParserException("Can't instantiate parser without input parameter");
      self::$instance = new ParamsParser($pParam) ;
    }
    return self::$instance;    
  }
  
  
  /**
   * Checks whether the sequence of arguments is acceptable by this configuration
   *
   * @param Array $tree
   * @param ParamsParser $pParser
   * @return boolean
   */
  public static function isAcceptable($tree, ParamsParser $pParser = NULL) {
    static $parser;
    static $initialTree;
    if ( ($parser instanceof ParamsParser) == false && !is_null($pParser)) $parser = $pParser;
    if ( is_null($initialTree) ) $initialTree = $tree;
  
    try {
      $t = $parser->currToken();
    }
     catch(OutOfBoundsException $e) {
       return true;
     }
   
    if (!array_key_exists($t,$tree)) {
      if (self::$debug) {
    		echo "ACT_CONTR TOKEN " , $t, " doesn't exist in ";
      	print_r($tree);
      }
      return false;
    }

    if ( is_array($tree[$t]) ) {
      //echo "&nbsp;&nbsp;&nbsp; in array<br>";
      try {
        $parser->next();
      }
       catch(OutOfBoundsException $e) {
         //support a call default action if controller is called without an action:          
         return true; // false;
       }
      $arrRes = self::isAcceptable($tree[$t]);
      if ($arrRes == false) return false;
      else return self::isAcceptable($tree);
    }
     elseif ( is_null($tree[$t])) {
        //echo "&nbsp;&nbsp;&nbsp; in null<br>";  
        try {$parser->next();} catch (OutOfBoundsException $e) {}  // if out of bounds - then it's a last token
        return self::isAcceptable($tree);
      }
     elseif( is_numeric($tree[$t]) && $tree[$t] == -1 ) {
       // allow one optional param
       $lim = abs($tree[$t]) + 1;
       try {
         for ( $i = 0 ; $i < $lim ; $i++ ) {
           $parser->next();
           // make sure that next token isn't a controller (optional param is omited)
           if (array_key_exists($parser->currToken(), $initialTree) == false) $parser->next();
         }
       } catch (OutOfBoundsException $e) {return true;} 
       return true;
     }
      
     elseif( is_numeric($tree[$t]) ) {
       //echo "&nbsp;&nbsp;&nbsp; in numeric<br>";
       try { 
         for( $i = 0 ; $i < $tree[$t] ; $i++ ) $parser->next();
       }
        catch (OutOfBoundsException $e) {return false;} 
       try {$parser->next();} catch (OutOfBoundsException $e) {}  // if out of bounds - then it's a last token
       return true;
     }
  }
  
}


class ParamsParserException extends Exception{};